Skip to content

docs(plugin-charts, plugin-form, plugin-list, app-shell): census of the object-schema snake lookup reads — all seven legs keep, with the evidence recorded at each site - #7649

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7642-object-schema-snake-lookup-census
Sep 5, 2026
Merged

docs(plugin-charts, plugin-form, plugin-list, app-shell): census of the object-schema snake lookup reads — all seven legs keep, with the evidence recorded at each site#7649
os-sam merged 3 commits into
mainfrom
claude/issue-7642-object-schema-snake-lookup-census

Conversation

@os-sam

@os-sam os-sam commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Part of #7642

Census only. Zero sites retired, zero runtime change (+145 / −0 at 95872adb7 against the
merge-base 1ec291c0: // comments plus one empty-frontmatter changeset; 0 executable tokens
moved, measured — see Verification). Per the dispatching ruling, the census is the deliverable
and the retirements were conditional on it.

Verdict: all seven legs KEEP

The card's argument is that FieldSchema is strict, the four snake spellings are
absent, therefore no producer can emit them. Measured against the spec installed in
this repo, with all controls lit, that is true — of the authoring path. Three
further measurements moved every site to KEEP.

1. The serve path runs no parse

ObjectStackAdapter.getObjectSchema returns the server document verbatim plus exactly
two rewrites (normalizeSchemaReferenceKeys, applyFieldWidgetOverrides). Count of
ObjectSchema.parse/safeParse on that path: 0, against a lit control (the same
file does use DroppedFieldsEventSchema and isFilterAST). So FieldSchema's
strictness gates authoring, not serving: a stored pre-strict document still
delivers these keys to every one of these consumers. The legs are not unreachable.

Corrected 2026-09-05: the resolveActionParams site is served by a different path —
useMetadata().objects, filled by client.meta.getItems(type) in app-shell's
MetadataProvider (line 648 on this branch) — and that path runs no schema parse either:
the provider's only parse is JSON.parse of its session cache (control: the same file
imports and uses expandViewContainer from the spec), and the pinned
@objectstack/client@17.2.0 dist carries exactly 3 safeParse calls, all event-payload
schemas (MetadataEventSchema, DataEventSchema, BulkDataEventSchema), none on
getItems (control: unwrapResponse 166 hits). The conclusion held; the cited path was
wrong for that one site.

2. Five of the six sites have no camelCase leg at all

Measured per file, control lit. Only ObjectFieldInspector reads a camel spelling off
the field def. For the other five, retiring the snake read does not re-point the read
to the declared spelling — it deletes the only read of the value.

The corollary is the more interesting half, and it inverts the card's disposition:
those five sites already ignore a spec-compliant displayField /
descriptionField / lookupFilters today. The user-serving change is to add those
three declared camel reads, not to remove the snake ones. idField is not one of
them
(corrected 2026-09-05 after contract review): measured on the pinned spec 17.2.0,
FieldSchema REJECTS idField with unrecognized_keys exactly as it rejects id_field
— the spec's only idField sits on InlineGridColumnSchema, a different shape — so the
id_field reads at ObjectChart, ListView (object-def branch), UserFilters and
resolveActionParams have no FieldSchema spelling to gain a leg for, and adding an
idField read would fossilise a spelling FieldSchema does not declare. That is a
FieldSchema statement only (scoped 2026-09-05, round 2): the widget contract
@object-ui/types LookupFieldMetadata.idField (packages/types/src/field-types.ts:525
on main, read by LookupField.tsx:262) is declared — a widget-contract key that
PR #7641 deliberately kept, on the same runtime object §3 describes — but it is a
different contract, not a FieldSchema target. Their route is the ingestion choke point
(objectui#7650, option A), not an additive camel leg. objectui#7435 already carries that
finding for two of the five; this census adds three more (deriveMasterDetail,
ListView's object-def branch, UserFilters).

3. The object-schema def and the widget bag are the same object at runtime — and what the widget reads moved during review

ObjectForm builds its fields from getObjectSchema and threads each def to the widget
(ObjectForm.tsx is byte-identical on origin/main and this head, so the hop itself
did not move). The widget half of this finding is dated, because PR #7641 landed
while this PR was in review:

  • At this branch's base (1ec291c0, 2026-09-04T13:55Z): LookupField read
    display_field / description_field / id_field / lookup_filters snake-first
    (:260-262, :278); LookupFieldMetadata in @object-ui/types — published — declared
    all four snake members; content/docs/fields/lookup.mdx documented three of them as
    authorable (description_field 3, id_field 1, lookup_filters 3; display_field 0,
    reference_field 4 as the control). That was the objectui#7155 shape exactly: the
    "no producer can emit it" argument true of the object contract and false of a second
    published one.
  • On origin/main (a3eb5d07), fetched and measured 2026-09-05 with predictions
    written before each probe, 11/11 met:
    PR feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641merged 2026-09-04T15:01:32Z as
    351eb318
    , 30 files, 0 of them this PR's — converged the widget contract on the
    spec's camelCase. LookupField.tsx:260-262/:278 read displayField || reference_field,
    descriptionField, idField, lookupFilterscamel-only (snake spellings in the
    file: 0; reference_field 1 as the control); LookupFieldMetadata
    (field-types.ts:516/:525/:539/:561) declares the camel members only (0 snake keys
    inside the interface); content/docs has 0 hits for all four snake keys (controls
    in the same run: reference_field 4, lookupFilters 3).

So on the tree this PR lands in, the split runs the other way: it is keeping
these six snake legs, while the form widget reads camel-only, that lets one stored
pre-strict document render one way in the form and another in the chart, list,
filters and action dialogs. The second published contract no longer declares the snake
spellings, so that half of the #7155 argument is gone on main. KEEP still stands,
on §1 and §2 (the serve path delivers the stored key, and five sites have no camel leg,
so retiring the read deletes the only read) and on the dispatching ruling that refused
option B and made the ingestion choke point (option A, objectui#7650) the prerequisite
for any retirement. The way to close the split is A plus the additive camel legs on
objectui#7435 — not a consumer-side deletion.

The census

site bag, traced to its producer contract camel leg? verdict
plugin-charts ObjectChart ds.getObjectSchema(schema.objectName), sole producer; the function is not in the package's public API object-schema def no KEEP
plugin-form deriveMasterDetail dataSource.getObjectSchema(d.childObject) in-repo, but deriveColumns is a public export so external bags are untraceable (two reads: deriveColumns and hydrateColumns, both marked) object-schema def in-repo, ambiguous outside no KEEP
plugin-list ListView, columns branch the view's own schema.columns ListColumnSchema, a third contract n/a, both castings refused KEEP
plugin-list ListView, object-def branch dataSource.getObjectSchema(...), sole producer object-schema def no KEEP
plugin-list UserFilters objectDef, a public prop typed loosely on a publicly exported component untraceable past this package no KEEP
app-shell resolveActionParams useMetadata().objects, the meta-API object documents object-schema def no KEEP
app-shell ObjectFieldInspector field def in the metadata-admin designer; writes camel back object-schema def yes, camel first KEEP

The card's table lists ListView as one site. It is two, on two different
contracts: the columns branch reads a list-view column, where ListColumnSchema
refuses display_field and displayField, id_field and idField,
reference_to and reference — measured, with a positive and a negative control.
That is objectui#7531's population, not this card's.

Measurements

FieldSchema.safeParse, installed spec, every control lit:

CONTROL A  {type:'lookup',name,label,reference}       ACCEPTED   (instrument live)
CONTROL B  displayField / descriptionField /
           lookupFilters                              ACCEPTED   (camel declared)
CONTROL C  zzz_not_a_real_key                         REJECTED unrecognized_keys
TEST       display_field / description_field /
           id_field / lookup_filters /
           reference_field / reference_to             REJECTED unrecognized_keys
TEST       idField                                    REJECTED unrecognized_keys

Re-measured 2026-09-05 (remediation), predictions written before the run, all five met,
MISSED PREDICTIONS: none; the spec resolved through this worktree's package.json to the
lockfile pin @objectstack/spec@17.2.0. The idField row is the one the first version of
this body did not carry, and the one that falsified its "declared camel spelling" claim.

ListColumnSchema.safeParse, same controls: minimal column ACCEPTED, bogus key
REJECTED, and all ten keys tested (both castings) REJECTED with
unrecognized_keys.

ObjectSchema.fields is a record; an array is refused with
invalid_type: expected record, received array — noted because two of these sites
carry an Array.isArray(objectDef.fields) branch the object contract cannot produce.

One live disagreement at the base — retired on main, so nothing to file

At the base the designer (ObjectFieldInspector, readLookupFilters) read
lookupFilters ?? lookup_filters (camel first) while the runtime LookupField read
lookup_filters ?? lookupFilters (snake first), so a document carrying both keys with
different values was displayed one way and honoured the other. PR #7641 (merged
2026-09-04T15:01:32Z, 351eb318) made LookupField read lookupFilters only, so both
halves now honour the camel key; the PM's recorded fallback on objectui#7642 ("a card
the moment #7641 stops being its fix") is moot. The designer's snake leg survives as a
read of a stored pre-strict document — the ground of its KEEP — not as one side of a
competing read order. The in-tree comment at ObjectFieldInspector.tsx:1149-1152 now
says exactly that. No card was filed for the inversion (a targeted search returned 0
against a lit control), and none is owed.

Clause-2 determination

No — and it holds for the diff actually shipped, which is comments plus a
changeset. Worth recording, though, that the proposed retirement would not have been
Clause-2-no in the assumed way: because five sites carry no camel leg, removing the
snake reads would have removed the only read of four authorable keys, a user-visible
behaviour change rather than a dead-code deletion.

Remediation 2026-09-05 (round 1) — contract review REFUSE, text only

The isolated contract review (comment 5548607662) refused this PR on two false factual
claims and recorded two non-blocking imprecisions. Commit 8c99d10d7 corrects the text;
no code, test or behaviour changed — proven by stripping every //-comment line from
ObjectChart.tsx at the base 09a2726a and at 8c99d10d7 and comparing hashes
(8b9ebbf582ea both, and also equal to origin/main's; control: injecting one code line
moves it to 82492a4b8841). git diff --stat 09a2726a..8c99d10d7: .changeset/lucky-donkeys-shave.md
+21/-7 and packages/plugin-charts/src/ObjectChart.tsx +6/-2, and 0 of the .tsx hunk lines
are non-comment.

  1. idField is NOT a declared camel spelling — four carriers named, all corrected:
    this PR body (sections 1-3 and the measurements block above); the changeset (finding 2
    now says idField is not in the list and routes id_field to objectui#7650 option A);
    the in-tree ObjectChart.tsx comment (now: adding a displayField leg is the
    contract-shaped change, and idField is refused exactly like id_field); and the
    census comment on objectui#7435 (corrected by a follow-up comment there, since comments
    cannot be edited from this seat). The PM's ruling comment on objectui#7642 carries the
    same phrase and is corrected by the remediation report comment on that card.
  2. The changeset said the inversion bug was "filed" — round 1 changed that to
    "deliberately NOT filed" but called PR feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 open; the second review caught that feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641
    had merged eleven hours before that sentence was written. Corrected in round 2 below.
  3. Non-blocking imprecisions, both re-measured and corrected in place above: the
    resolveActionParams serve path, and the docs over-statement.

Follow-up C ("add the declared camel legs", blessed on objectui#7642) is narrowed to
displayField / descriptionField / lookupFilters on objectui#7435, where that scope
lives; id_field is excluded and routed to objectui#7650 option A, with a routing note
added there. Remediation session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3.

Remediation 2026-09-05 (round 2) — the record dated against main, text only

The second contract review (comment 5548785535) refused the record for asserting in the
present tense that PR #7641 was open, and for describing a snake-first LookupField that
main no longer has. Commit 95872adb7 corrects the text; no code, test or behaviour
changed
. Method fix applied: every present-tense claim in this record was re-measured
against current origin/main (a3eb5d07, fetched first — not this branch's merge-base),
and the state and merge time of every PR named was read before choosing a tense.

  1. Changeset finding 3 and the inversion paragraph: dated to the base, the post-feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641
    state added, and the direction of the split corrected (on main it is keeping the six
    snake legs while the form reads camel-only that splits a stored document).
  2. ObjectFieldInspector.tsx:1149-1152: the "live inversion … runtime reads snake first"
    sentence replaced with the post-feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 fact.
  3. This body: §3, the disagreement section, the opening count and Scope, aligned with 1-2.
  4. "Neither spelling of the id key is declared" scoped to FieldSchema in §2 and in
    changeset finding 2, with LookupFieldMetadata.idField named as the declared
    widget-contract spelling. The objectui#7435 follow-up comment (5548681329) carries the
    same sentence and is corrected by a further comment there, which also gives the
    dispatcher of that card the per-key reading its triage predates.
  5. deriveMasterDetail.ts: the second snake read, in hydrateColumns (:311), now carries
    its own marker (:309-310) and is named by the deriveColumns comment (:254).

Verification

Gate union re-run at 09a2726a, the first commit:

check:control-bytes             exit 0   OK (scanned 6247 tracked text files)
check:designer-field-key-parity exit 0   designer-field-key-parity: OK
check-changeset-presence        exit 0   6 source files, 1 changeset, empty frontmatter
check-changeset-no-major        exit 0   no changeset declares a major bump
  • Dependency closure built, then type-check for app-shell, plugin-list,
    plugin-charts, plugin-form — all four echoed the script name (Scope: 4 of 47 workspace projects) and passed, so this is not a zero-match false green. The script
    in this repo is spelled type-check, not typecheck.
  • Targeted vitest from the repo root: 5 files, 107 tests, all passed.
  • eslint over the 6 changed files: 0 errors. The 303 warnings are pre-existing —
    0 lint messages land on any of the 44 added lines. The narrowing is a
    measurement, not a skip: file count read from --format json (6), and the flat
    config declares no project/projectService, so no file's verdict can depend on
    another file's contents.
  • Heavy runs went through the shared verify lock, slot issue-7642.

Re-run at 8c99d10d7, the round-1 commit (exit captured by redirect before any pipe,
verdict lines quoted):

check:control-bytes              exit 0   check-control-bytes: OK (scanned 6247 tracked text file(s); skipped 85 binary)
check-changeset-presence         exit 0   Every one of them has an EMPTY frontmatter — declared as releasing nothing
check-changeset-no-major         exit 0   No changeset declares a `major` bump
check-changeset-fixed            exit 0   All workspace packages are in the changeset fixed group
check-changeset-overwrite        exit 0   No pre-existing changeset was modified or deleted
check:designer-field-key-parity  exit 0   designer-field-key-parity: OK
  • Dependency closure built (Scope: 9 of 47 workspace projects) then type-check for
    plugin-charts, echoing tsc --noEmit && tsc -p tsconfig.test.json: VERDICT command-exit 0 · held the lock 54s · waited 2s.
  • eslint on ObjectChart.tsx (--format json, 1 file): 0 errors, 58 pre-existing
    warnings, 0 messages on the edited comment lines 204-210.

Re-run at 95872adb7, the round-2 commit (exit captured by redirect before any pipe,
verdict lines quoted):

check:control-bytes              exit 0   check-control-bytes: OK (scanned 6247 tracked text file(s); skipped 85 binary)
check:designer-field-key-parity  exit 0   designer-field-key-parity: OK
check-changeset-presence         exit 0   EMPTY frontmatter … the explicit exemption and a complete answer to this gate
check-changeset-no-major         exit 0   No changeset declares a `major` bump
check-changeset-fixed            exit 0   privatePackages declared: version=true, tag=false
check-changeset-overwrite        exit 0   No pre-existing changeset was modified or deleted
  • Executable-change instrument (TypeScript 5.9.3 from the pinned store): per file,
    R1 = sha1 of the transpileModule emit with removeComments, R2 = sha1 of the AST
    leaf-token sequence (JSDoc nodes excluded) plus the token count. All six census files
    read identical at 8c99d10d7, at origin/main (a3eb5d07) and at 95872adb7:
    resolveActionParams.ts 1589 tokens, ObjectFieldInspector.tsx 11197,
    ObjectChart.tsx 5997, deriveMasterDetail.ts 2580, ListView.tsx 18098,
    UserFilters.tsx 5607. Lit control on ObjectFieldInspector.tsx: injecting
    const __ctl_code = 1; moved R1, R2 and the count by +5 tokens; injecting a // line
    or a /* */ block moved neither. git diff -U0 8c99d10d7..95872adb7 under packages/:
    0 added or removed lines that are not // comments; max added line width 88;
    raw control bytes in the three touched files 0.
  • eslint on the two touched source files (--format json, 2 files): 0 errors, 22
    pre-existing warnings, 0 messages on the edited lines (1149-1152; 254, 309-310).
  • Declared narrowing: type-check and vitest not re-run for this commit — the token
    instrument above is the measurement that nothing executable moved; CI runs the farm.

Scope

packages/types/**, plugin-grid/relationalMetaKeys.ts and the widget-metadata bag
were not touched. PR #7641 (merged 2026-09-04, 351eb318) touched 30 files, none of this
PR's 7; main has moved 80 files since the base, 0 of them this PR's, so the PR merges
cleanly — the only conflict was the record versus the tree it lands in, fixed above.
reference_field is reported on but not retired.

Labelled needs:contract-review on both carriers, as the dispatching ruling directs
when the census concludes a second published contract declares these spellings. That
trigger was true at the base and is dissolved on main by #7641 (LookupFieldMetadata
is camel-only there); the labels stay for the review round to clear — not cleared here.


Generated by Claude Code

…object-schema snake-lookup census at each site, retiring none

Census only, no runtime change. Each of the six sites now carries the verdict for
the bag it actually reads, traced to its producer.

Three measurements moved every site to KEEP:

1. The serve path runs no parse. `ObjectStackAdapter.getObjectSchema` returns the
   server document verbatim plus `normalizeSchemaReferenceKeys` and
   `applyFieldWidgetOverrides` only, so a stored pre-strict document still delivers
   these keys. `FieldSchema`'s strictness gates authoring, not serving.
2. Five of the six sites have no camelCase leg, so retiring the snake read deletes
   the only read of the value rather than re-pointing it — and, conversely, those
   five already ignore a spec-compliant `displayField`/`idField`/`descriptionField`/
   `lookupFilters` today (objectui#7435 for two of them; this census adds three).
3. The object-schema field def and the widget bag are the same object at runtime:
   `ObjectForm` threads each `getObjectSchema` field def to the widget, where
   `LookupField` reads the snake spellings first and `LookupFieldMetadata` declares
   all four. Retiring here would split one stored document's rendering between the
   form and the chart/list/filter/action consumers.

One site of the six is not the object-schema def at all: `ListView`'s declared-columns
branch reads a list-view column, and `ListColumnSchema` refuses both castings of every
key involved (objectui#7531).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.3 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-C7hWyQsx.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.70KB 2.68KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.40KB 61.26KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 48.04KB 13.35KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.59KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.41KB 41.04KB
plugin-grid (index.js) 210.74KB 56.94KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.57KB 6.82KB
plugin-markdown (index.js) 13.70KB 4.68KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.34KB 3.23KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

os-sam commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator Author

契约复核 — 裁决:REFUSE(逐字采纳)

归属:domain:ui 派发席位,依 2026-08-31 裁定席内完成;董事席链条为可选事后审计,非放行必要条件;⛔ 无「等外部席位」的无限期状态。

档位与核验:隔离 fable 子代理(⛔ 契约复核不适用额度耗尽豁免降档)。transcript 中 harness 逐消息盖章的 model 字段 89 次读数全部 claude-fable-5-1,0 次回退。亮控:同一 grep 在本会话其它 transcript 上读出 claude-opus-5(30 份)与 <synthetic>(1 份)—— 该 grep 能读出非 fable 值,所以这里的零是读数。达标 ⇒ 逐字采纳。⛔ 不修剪、不润色、不挑着采纳。

独立性:只喂卡片 #7642、既有裁定与 PR 本身;⛔ 未喂派发简报、⛔ 未喂本席结论;简报按对抗性写法。

needs:contract-review 两个载体都不清除。

这一条对本席自己也是判决。 复核量出 idField 在 pin 住的 spec 17.2.0 上被 FieldSchemaunrecognized_keys 拒绝,和 id_field 一样 —— 而本席在裁 A/B/C/D 时判了 C(「补上已声明的驼峰腿」)并把 idField 算在「已声明」里。那个前提是假的,所以 C 这条后续裁定必须收窄成 displayField/descriptionField/lookupFilters,id_field 只能走 A。这正是本卡这一族要挡的化石化,而本席差点亲手加了一条。已按复核意见处置,见下。


All measurements are in; no further tool calls needed. Verdict follows.

① Derived judgments — accept set / published surface

Diff shape (measured, not from the body): git diff 1ec291c0..09a2726a → 7 files, 102 insertions, 0 deletions. Under packages/: 44 added lines (control reads 44), 0 added lines that are not // comments, 0 export lines touched. Plus one changeset with empty frontmatter. So this diff introduces no accept-set change and no published-surface change. The remaining "derived judgments" are the seven KEEP verdicts the census ships as in-tree comments, graded individually:

  1. packages/plugin-charts/src/ObjectChart.tsx — KEEP right. Bag traced: ds.getObjectSchema(schema.objectName) (line 659) → objectSchema?.fields?.[groupByField]. Reads off fieldDef: id_field, reference_field, display_field only — no camel leg. resolveGroupByLabels is not re-exported by src/index.tsx (exports ObjectChart, ObjectChartBlock) and package.json exports only ".". But the shipped comment says "Adding the declared idField/displayField" — idField is not declared; see the FieldSchema probe below.
  2. packages/plugin-form/src/deriveMasterDetail.ts — KEEP right. deriveColumns is public (src/index.tsx:68); in-repo producer MasterDetailForm.tsx:582 dataSource.getObjectSchema(d.childObject)deriveDetail (612) → deriveColumns (461). No d?.displayField leg.
  3. packages/plugin-list/src/ListView.tsx columns branch — KEEP right, third-contract classification right. ListColumnSchema (spec 17.2.0 /ui): minimal column ACCEPTED, zzz_not_a_real_key REJECTED, and all 11 keys tested (display_field/displayField, description_field/descriptionField, id_field/idField, reference_field, reference, reference_to, lookup_filters/lookupFilters) REJECTED unrecognized_keys.
  4. ListView.tsx object-def branch — KEEP right. Sole non-null producer setObjectDef(def) at 1416 from dataSource.getObjectSchema (1414). No camel leg (line 72 'descriptionField' is FLAT_MAP_CONFIG_KEYS, unrelated).
  5. packages/plugin-list/src/UserFilters.tsx — KEEP right. objectDef?: any on UserFiltersProps (line 90); exported at src/index.tsx:19-20. The f.displayField at 597 is the local resolved descriptor, not a field-def read.
  6. packages/app-shell/src/utils/resolveActionParams.ts — KEEP right, with one imprecision. ctx.objects provenance verified: AppContent.tsx:172 useMetadata()RecordDetailView:588; ObjectView:865 prop → useConsoleActionRuntime; DeclaredActionsBar:438 useMetadataItemuseMetadata().getItem. Lines 246-251 (displayField: fromField …) are the RESOLVED_ONLY_PARAM_KEYS message table, not reads — no camel leg. Imprecision: this site's serve path is client.meta.getItems('object') (MetadataProvider.tsx:649), not ObjectStackAdapter.getObjectSchema; the PR measured only the latter. I measured the actual path: MetadataProvider.tsx has no schema parse (only JSON.parse of the session cache at 515; expandViewContainer at 413 is the lit control); pinned @objectstack/client@17.2.0 dist getItems is fetch + unwrapResponse, and its 3 safeParse calls are MetadataEventSchema/DataEventSchema/BulkDataEventSchema, none on that path. So the comment's conclusion holds — by a measurement the PR did not make.
  7. ObjectFieldInspector.tsx — KEEP right. Camel-first at 1145; writes patchDef({ lookupFilters }) at 1196-1200.

Cross-site claims:

② Semver grading

Changeset .changeset/lucky-donkeys-shave.md declares ---\n--- (empty frontmatter = no bump). Correct level: none — comment-only inside function bodies, no export, no .d.ts, no runtime. AGENTS.md line 161 and scripts/check-changeset-presence.mjs ("The empty-frontmatter exemption") make this a first-class pass; Changeset Declaration and Changeset Bump Policy checks are green. Consistent. The changeset body, however, carries two false statements (idField "configured/spec-compliant"; bug "filed").

③ Boundary-flag disposition

  • Open question A/B/C/D (PM ruled C then A; B refused): the ruling is sound except C is mis-scoped. "Add the declared camel legs" for all four keys is impossible: idField is undeclared on FieldSchema (measured above), so C as written would add an undeclared read at ObjectChart, ListView, UserFilters and resolveActionParams — the exact fossilisation this family exists to stop. Escalate: the C follow-up card must be narrowed to displayField/descriptionField/lookupFilters, with id_field handled under A only.
  • Clause-② determination: my own — no contract change in this diff (comment-only, proven mechanically). Agrees with the report. The "record on the card" sub-question is already satisfied by the report comment on finding(plugin-charts, plugin-form, plugin-list, app-shell): six sites read the retired snake lookup keys off an OBJECT-schema def, where FieldSchema refuses them #7642.
  • Untraceable external bags (deriveColumns, UserFilters): correctly kept; both are public surface (plugin-form/src/index.tsx:68, plugin-list/src/index.tsx:19-20).
  • Inversion bug left to feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641: acceptable given the PM recorded it, verified real (ObjectFieldInspector:1145 camel-first vs LookupField.tsx:278 snake-first) — but the changeset must not say "filed".
  • Serve-path claim on the resolveActionParams site: answered by my measurement of meta.getItems (no parse); the PR should cite that path, not getObjectSchema.

Verdict

VERDICT: REFUSE — two false factual claims ship in the record: (1) the PR body, changeset, the #7435 comment and the in-tree ObjectChart.tsx comment call idField a declared/spec-compliant camel spelling; measured on the pinned spec 17.2.0, FieldSchema REJECTS idField (unrecognized_keys) exactly as it rejects id_field, and the blessed follow-up C rests on that error; (2) changeset line 55 says the designer/runtime inversion bug was "filed" — no card exists, and the PR body and report say none was filed. Both are text-only fixes; no code change needed.

Implemented-by: claude/issue-7642-object-schema-snake-lookup-census (head 09a2726a)
Reviewed-by: isolated subagent (running under seat session_01KbJQ1y1J12nZxYzFWhP8Q3, the same seat id the implementer carries — the review context was separate)


Generated by Claude Code

…act review — idField is not a declared FieldSchema spelling, and the inversion bug was not filed

Text-only remediation of the REFUSE verdict on PR #7649
(issuecomment-5548607662). No code, test or behaviour change: the only
.tsx delta is comment text, proven by stripping //-comment lines from
ObjectChart.tsx at the base and at this commit and comparing hashes.

1. idField is NOT a declared camel spelling. Re-measured on the pinned
   @objectstack/spec@17.2.0 with lit controls (minimal lookup def ACCEPTED;
   displayField / descriptionField / lookupFilters ACCEPTED; bogus key
   REJECTED unrecognized_keys): FieldSchema REJECTS idField with
   unrecognized_keys exactly as it rejects id_field. The spec's only idField
   sits on InlineGridColumnSchema, a different shape. The in-tree
   ObjectChart.tsx comment and the changeset no longer list idField beside
   displayField as a declared spelling; the id_field reads are routed to
   the ingestion choke point (objectui#7650, option A), not to an additive
   camel leg.
2. The changeset said the designer/runtime lookupFilters inversion bug was
   "filed". No card exists (targeted search, control lit). It now says the
   bug was deliberately NOT filed, and why.
3. Two non-blocking imprecisions, both re-measured: the resolveActionParams
   site is served by useMetadata().objects via client.meta.getItems in
   app-shell's MetadataProvider (no schema parse there either; the client's
   three safeParse calls are all event-payload schemas), and
   content/docs documents three of the four snake keys as authorable —
   display_field has zero hits in content/docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI4V3Zxi.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Contract re-review (round 2) — claude-fable-5-1, adopted verbatim

Isolated review subagent. Tier verified from its transcript: 69/69 assistant turns stamped claude-fable-5-1, 0 fallback notices. Lit control: the same reader returns claude-opus-5 5810 times across the 102 transcripts of this session, so the zero is a reading and not a dark instrument. The text below is adopted verbatim — not edited, trimmed or paraphrased.


All measurements are in; nothing further to request. Scratch worktree and probe files are removed, and git status on the primary checkout is empty.

Contract re-review — objectstack-ai/objectui PR #7649 (card #7642), head 8c99d10d77246dbd2026aae7220d03282e823aef

Seat & method. Independent re-review, read-only: no GitHub writes, no edits to either primary checkout (a detached scratch worktree at the head was used for the repo's own gates and removed afterwards). Base 1ec291c0 = merge-base(head, origin/main); origin/main = a3eb5d07, 10 commits ahead of the base. Every number below is my own reading; the author's and the prior reviewer's numbers were not reused.

Instruments and their lit controls.

  • Executable-change instrument (TypeScript 5.9.3, from the pinned store): two readings per file — R1 = sha1 of transpileModule emit with removeComments:true; R2 = sha1 of the AST leaf-token sequence (JSDoc nodes excluded) plus token count. Control on head ObjectChart.tsx: injecting const __ctl_code = 1; moved R1 and R2 and the count 5998→6003; injecting a // line or a /* */ block moved neither. The instrument reads code and ignores comments.
  • Contract probe on @objectstack/spec@17.2.0 dist (lockfile pin @objectstack/spec@17.2.0, pnpm-lock.yaml:4273; dist at /home/user/objectui-6349/node_modules/.pnpm/@objectstack+spec@17.2.0_ai@7.0.65_zod@4.4.3_/node_modules/@objectstack/spec), predictions P1–P7 written into the script before it ran. Controls: minimal lookup def ACCEPTED; displayField/descriptionField/lookupFilters ACCEPTED; zzz_not_a_real_key REJECTED unrecognized_keys. MISSED PREDICTIONS: none.
  • Grep instruments each carry a positive control in the same run (named inline).

Measured facts the verdict rests on

  1. Diff shape 1ec291c0..8c99d10d7: 7 files, +120/−0. Under packages/: 48 added lines, 48 match ^\+\s*//, 0 other, 0 removed. R1/R2/token-count identical at base, 09a2726a and head for all six files (resolveActionParams.ts 1590 tokens, ObjectFieldInspector.tsx 11198, ObjectChart.tsx 5998, deriveMasterDetail.ts 2581, ListView.tsx 18099, UserFilters.tsx 5608). Remediation commit 09a2726a..8c99d10d7: .changeset/lucky-donkeys-shave.md +21/−7, ObjectChart.tsx +6/−2, both readings unchanged. The "text-only" claim is TRUE by measurement; no scope breach.
  2. FieldSchema 17.2.0: display_field, description_field, id_field, lookup_filters, reference_field, reference_to each REJECTED unrecognized_keys[<key>]; idField REJECTED unrecognized_keys[idField]. Structural second reading: FieldSchema.shape has 71 keys, contains displayField and lookupFilters, contains neither idField nor id_field. The spec source's only idField declaration is packages/spec/src/data/field.zod.ts:836 inside InlineGridColumnSchema (opened at :803); control displayField 4 declarations. The prior REFUSE's ground (1) is confirmed RIGHT.
  3. Inversion-bug card: semantic search for the designer-camel-first/runtime-snake-first disagreement returns finding(plugin-grid, fields): the snake_case half of the lookup dialect — display_field, description_field, lookup_filters, id_field — is refused by the strict FieldSchema, so no spec-compliant producer can emit it #7155, Field designer offers a referenceTo control for lookup fields — a key FieldSchema rejects by name, so saving a lookup field hard-blocks the object #6041, Console lookup 下拉「最近使用」区按 id 直查,绕过 lookupFilters 与 dependsOn 级联过滤 #5195, [fields] Lookup chip 显示值走 autonumber 启发式,与 picker(nameField/titleFormat)解析不一致 #2357 — none is that card; control query returns finding(data-objectstack): the object-schema SERVE path runs no parse, so FieldSchema strictness is not evidence that a refused key cannot reach a consumer #7650 first; the created-since-2026-09-04T14:00Z listing carries none. The prior REFUSE's ground (2) was RIGHT; the head's "deliberately NOT filed" is TRUE.
  4. PR feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 is MERGED, not in flight: merge commit 351eb3181 committed 2026-09-04T14:46:52Z, merged_at 2026-09-04T15:01:32Z (by os-warren), 30 files, 0 overlap with this PR, not an ancestor of this PR's base (1ec291c0, 13:55:58Z) or head, is on origin/main. The prior REFUSE (02:03:04Z) and the remediation commit (02:12:56Z) were both written after that merge and both call feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 in flight / open.
  5. What feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 did to the facts this record asserts (head vs origin/main):
    • packages/fields/src/widgets/LookupField.tsx — head :260-262/:278 read display_field || displayField, description_field ?? descriptionField, id_field, lookup_filters ?? lookupFilters (snake-first); main :260-262/:278 read displayField || reference_field, descriptionField, idField, lookupFilterscamel-only (reference_field remains).
    • packages/types/src/field-types.ts LookupFieldMetadata — head :516/:525/:539/:561 declare display_field/id_field/description_field/lookup_filters; main same lines declare displayField/idField/descriptionField/lookupFilters. The snake members are gone.
    • content/docs — head: display_field 0, description_field 3, id_field 1, lookup_filters 3, reference_field 4 (control); main: all four snake keys 0, reference_field 4, lookupFilters 3.
    • ObjectFieldInspector.tsx:1145 on main still reads def.lookupFilters ?? def.lookup_filters. So on main the designer/runtime inversion is retired (both honour camel), while the form widget now ignores a stored snake document that chart/list/filters/actions still honour.
  6. Serve paths: packages/data-objectstack/src/index.ts .parse(/safeParse( count 0 (controls: DroppedFieldsEventSchema import :15, isFilterAST :21/:266); getObjectSchema :3887 → metadataCache.getnormalizeSchemaReferenceKeys. packages/app-shell/src/providers/MetadataProvider.tsx:648 getClient().meta.getItems(type); only parse is JSON.parse :515 (control expandViewContainer :9/:413). @objectstack/client@17.2.0 dist: safeParse on exactly 3 schemas (MetadataEventSchema, DataEventSchema, BulkDataEventSchema; 6 textual hits across the dual build), getItems at dist/index.js:658/:4421 is fetch + unwrapResponse (control 332 hits).
  7. ListColumnSchema (/ui): minimal ACCEPTED, bogus REJECTED, all 11 keys (display_field/displayField, description_field/descriptionField, id_field/idField, reference_field, reference, reference_to, lookup_filters/lookupFilters) REJECTED unrecognized_keys. ObjectSchema.fields as array → invalid_type expected=record path=fields.
  8. CI at head: 32 check runs, 29 success, 3 skipped, 0 failing (Type Check, Lint, four test shards all success). Repo gates re-run by me on the head: check-changeset-no-major exit 0; check-changeset-presence exit 0 ("Every one of them has an EMPTY frontmatter … the explicit exemption").

① Derived judgments

Accept-set changes in the diff: 0. Published-surface changes in the diff: 0. Measurement: 48/48 added packages/ lines are // comments, 0 removals, R1/R2 identical at base and head for all six files, no .d.ts, barrel, package.json or packages/types/** touched. The judgments the PR ships are the seven in-tree KEEP verdicts plus the prose findings; each graded:

  1. packages/plugin-charts/src/ObjectChart.tsx KEEP — RIGHT. Bag: :672 ds.getObjectSchema(schema.objectName):673 resolveGroupByLabels(:117 objectSchema?.fields?.[groupByField]; reads :210 fieldDef.id_field || 'id', :221 fieldDef.reference_field || fieldDef.display_field || 'name'; no camel leg. resolveGroupByLabels is not re-exported (src/index.tsx:16 exports ObjectChart, ObjectChartBlock; package.json exports "." only; outside the package it appears only in prose: CHANGELOG.md:254, ROADMAP.md:1070, core/src/utils/chart-series.ts:696,:920). The remediated comment's idField sentence — RIGHT (fact 2).
  2. packages/plugin-form/src/deriveMasterDetail.ts KEEP — RIGHT. deriveColumns public at src/index.tsx:68; in-repo bag MasterDetailFormgetObjectSchema; no d?.displayField leg. Note: the file has two snake reads (:254 and :308 in hydrateColumns); the marker covers only the first. Non-blocking.
  3. packages/plugin-list/src/ListView.tsx columns branch KEEP, third-contract classification — RIGHT. f comes from schema.columns (:2699-2700); ListColumnSchema refuses both castings (fact 7).
  4. ListView.tsx object-def branch KEEP — RIGHT. Only non-null producer :1416 setObjectDef(def):1414 dataSource.getObjectSchema(schema.objectName) (:1403 sets null); no camel leg.
  5. packages/plugin-list/src/UserFilters.tsx KEEP — RIGHT. objectDef?: any (:90), exported at plugin-list/src/index.tsx:19-20; in-repo caller ListView.tsx:3195-3197 <UserFilters objectDef={objectDef}; Array.isArray(objectDef.fields) at :283 (and ListView.tsx:1074) is a branch the record contract cannot produce (fact 7).
  6. packages/app-shell/src/utils/resolveActionParams.ts KEEP — RIGHT, and the corrected serve-path text is RIGHT (fact 6). Provenance closed end to end: :458 ctx.objectsuseConsoleActionRuntime.tsx:221-223ObjectView.tsx:1192-1194console/AppContent.tsx:172 useMetadata():989/:995; and RecordDetailView.tsx:586-588AppContent.tsx:1005 / InterfaceListPage.tsx:263 useMetadata().
  7. ObjectFieldInspector.tsx KEEP — RIGHT on its stated ground (:1153 camel-first; writes camel back :1204-1208; a stored pre-strict document is the only thing the snake leg reads). But the shipped comment :1149-1152 — "NOTE the live inversion it participates in: the runtime reads lookup_filters ?? lookupFilters (snake first, @object-ui/fields LookupField)" — is WRONG on the tree this PR lands in (fact 5: main's LookupField.tsx:278 reads lookupFilters only). It was true at the PR's base and false from 351eb31 onward.

Prose findings, graded at the head:

② Semver grading

Declared: .changeset/lucky-donkeys-shave.md frontmatter is ---\n---\n (verified with cat -A) — no bump. Correct level: none — zero executable tokens moved, no export, no .d.ts, no runtime. AGENTS.md §版本号策略 (AGENTS.md:235-241): majors are refused (scripts/check-changeset-no-major.mjs, exit 0 here) and breaking changes ship as minor with the semantics in the body; neither applies. check-changeset-presence.mjs accepts the empty frontmatter as "the explicit exemption" (exit 0 on the head). Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report all green. Declared = correct. The body, however, is the platform's release-notes input (AGENTS.md:162: the platform's release criterion reads this repo's declared changesets), and its lines :33-37 and :67-71 assert facts that are false on main (①).

③ Boundary-flag disposition

Verdict

VERDICT: REFUSE

The prior refusal's two grounds are fixed and independently confirmed; the remediation is text-only by measurement; every KEEP is right. But this PR's entire deliverable is the record, and the record at the head ships statements that are false in the only tree it can land in, one of them authored in the remediation itself after the fact it misstates. Text-only fixes; no code, test or behaviour change is needed:

  1. .changeset/lucky-donkeys-shave.md:67-69 — replace "open PR feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 flips the runtime half and retires it on its own; … becomes a card the moment feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 stops being its fix" with the measured fact: feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 merged 2026-09-04 (351eb318); on main LookupField.tsx:278 reads lookupFilters only, so the inversion is retired on main; the designer's snake leg (ObjectFieldInspector.tsx:1153) survives as a stored-document read.
  2. .changeset/lucky-donkeys-shave.md:33-37 (finding 3) — date or past-tense the three sub-claims (snake-first LookupField, LookupFieldMetadata declaring all four snake keys, lookup.mdx documenting three) and add the post-feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 state: LookupField camel-only (reference_field kept), LookupFieldMetadata camel-only, content/docs 0 snake hits — and say that on main it is keeping these six snake legs, not retiring them, that splits a stored document between the form and chart/list/filters/actions; KEEP rests on findings 1-2 and the refusal of B / prerequisite A.
  3. packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx:1149-1152 — delete or rewrite the "live inversion … runtime reads lookup_filters ?? lookupFilters (snake first…)" sentence; it is false on main.
  4. PR body — §3, "One live disagreement", "Remediation" §2 ("PR feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 is in flight"), and the opening "(102 insertions, 0 deletions…)" (head is +120/−0): align with 1-3.
  5. (Same pass, non-blocking on its own) Scope "undeclared spelling" / "neither spelling of the id key is declared" to FieldSchema in the PR body §2, changeset finding 2, and note that LookupFieldMetadata.idField (packages/types/src/field-types.ts:525 on main, read by LookupField.tsx:262) is a declared widget-contract spelling on the same runtime object; the finding(plugin-charts, app-shell): two lookup readers have NO spec leg at all — displayField cannot reach ObjectChart or resolveActionParams, which read the snake spelling only #7435 follow-up comment (5548681329) carries the same sentence and can only be corrected by a further comment.
  6. (Non-blocking) deriveMasterDetail.ts:308 (hydrateColumns) is a second snake read in that file with no marker; either mark it or have the :249-253 comment name it.

Generated by Claude Code

…st main — #7641 merged and retired the LookupField snake-first read, so the inversion is gone and the split now runs the other way

Second contract-review round (PR comment 5548785535) refused the record for
asserting, in the present tense, that PR #7641 was open and in flight. It merged
2026-09-04T15:01:32Z as 351eb31, and on origin/main (a3eb5d0) LookupField reads
lookupFilters only, LookupFieldMetadata declares the camel members only, and
content/docs has 0 hits for all four snake keys.

Text only, zero executable tokens moved (transpile-emit and AST-token hashes of all
six census files identical before and after, lit control in the report):

- changeset finding 3: dated to the base, post-#7641 state added, direction of the
  split corrected (keeping the six snake legs while the form reads camel-only is
  what splits a stored document now); KEEP restated on findings 1-2 and the ruling
  that refused B and made the choke point (A) the prerequisite.
- changeset inversion paragraph: retired on main by #7641; nothing left to file.
- changeset finding 2: "neither spelling of the id key is declared" scoped to
  FieldSchema; LookupFieldMetadata.idField is a declared widget-contract key.
- ObjectFieldInspector.tsx readLookupFilters comment: the "live inversion" sentence
  replaced with the post-#7641 fact.
- deriveMasterDetail.ts: the second snake read in hydrateColumns is now named by the
  deriveColumns comment and carries its own marker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI4V3Zxi.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Contract re-review (round 3) — claude-fable-5-1, adopted verbatim

Isolated review subagent. Tier verified from its transcript: 65/65 assistant turns stamped claude-fable-5-1, 0 fallback notices, against a lit control that reads claude-opus-5 in this session's other transcripts. Adopted verbatim — not edited, trimmed or paraphrased.

This is the third round on this PR. Rounds 1 and 2 both refused; this reviewer re-measured both of their grounds independently, confirmed them, and confirmed they are fixed.


All measurements are in; the scratch worktree is removed and git status on the primary checkout is empty. Report follows.

Contract re-review (round 3) — objectstack-ai/objectui PR #7649 (card #7642), head 95872adb7

Seat & method. Independent, read-only: no GitHub writes, no edits to either primary checkout; a detached scratch worktree at the head was used only for the repo's own gates and removed. git fetch origin main was run first: origin/main = 6eebc54b6 (2026-09-05T06:09:06Z), 16 commits / 96 files past the merge-base 1ec291c0 (2026-09-04T13:55:58Z). Every present-tense claim below was measured against 6eebc54b6, not the merge-base and not the a3eb5d07 the record cites; the 18 files that moved a3eb5d07..6eebc54b6 include none of the files the record measures (listed by name: mobile, types/mobile, docs, skills guides, hooks, AGENTS.md).

Instruments, each with its lit control.

  • Executable-change instrument, built independently: per file, R1 = sha1 of ts.transpileModule emit with removeComments:true; R2 = sha1 of the AST leaf-token sequence (JSDoc excluded) plus token count. Run with TypeScript 5.9.3 (pinned store) and 6.0.3 — identical verdicts. Control at top-of-file on all six files: const __ctl_code = 1; moved R1, R2 and the count (+5) on every file; a // line and a /* */ block moved nothing on any file. (A first control run injected at file midpoint and landed inside JSX text on ObjectFieldInspector.tsx; it was re-run at top-of-file, reported not smoothed.)
  • Contract probe on the lockfile pin @objectstack/spec@17.2.0 (pnpm-lock.yaml:4273 on head and on main), resolved through a workspace package.json's exports map to dist/data/index.js and dist/ui/index.js; predictions written before the run. Controls: minimal lookup def ACCEPTED; zzz_not_a_real_key REJECTED unrecognized_keys; displayField/descriptionField/lookupFilters[eq] ACCEPTED. One missed prediction of my own: dependsOn:'org' REJECTED invalid_type expected array — the key is declared (shape has it; ['org'] ACCEPTED); my fixture type was wrong. Outside the record's claims.
  • Grep instruments carry a positive control in the same run, named inline.

Measured facts the verdict rests on.

  1. Diff 1ec291c0..95872adb7: 7 files, +145/−0. Under packages/: 51 added lines, 51 match ^\+\s*//, 0 other, 0 removed. Round-2 hunk 8c99d10d7..95872adb7 under packages/: +7/−4, all //. R1/R2/count identical at base, 8c99d10d7, head and origin/main 6eebc54b6 for all six files (resolveActionParams.ts 1590 tokens, ObjectFieldInspector.tsx 11198, ObjectChart.tsx 5998, deriveMasterDetail.ts 2581, ListView.tsx 18099, UserFilters.tsx 5608 — the record's 1589… are the same readings minus the EOF token). Raw control bytes in the 7 files: 0. Max added width 88 for the round-2 hunk (as the body says), 89 whole-PR (changeset line).
  2. PR feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641: merged_at 2026-09-04T15:01:32Z by os-warren; merge commit 351eb318 committed 14:46:52Z; ancestor of origin/main, not of the base or head; 30 files, 0 of this PR's 7. On main packages/fields/src/widgets/LookupField.tsx :260-262/:278 read displayField || reference_field, descriptionField, idField, lookupFilters — snake spellings in the file 0/0/0/0 (control reference_field 1); at base the same lines read snake-first (2/4/2/1). LookupFieldMetadata (packages/types/src/field-types.ts:502-562) on main declares displayField:516, idField:525, descriptionField:539, lookupFilters:561, 0 snake members (at base: the four snake members at the same lines). content/docs on main: 0/0/0/0 for the four snake keys (controls reference_field 4, lookupFilters 3); at base 0/3/1/3 with lookupFilters 0. ObjectForm.tsx blob identical main vs head (502c1b04); all six census files blob-identical base vs main.
  3. FieldSchema 17.2.0: display_field, description_field, id_field, lookup_filters, reference_field, reference_to, idField, titleFormat, title_format each REJECTED unrecognized_keys[<key>]; lookupColumns, lookupPageSize, dependsOn[] ACCEPTED. FieldSchema.shape has 71 keys, contains displayField/descriptionField/lookupFilters/lookupColumns/lookupPageSize/dependsOn/reference, contains none of titleFormat/title_format/idField/id_field/display_field/reference_to. ObjectSchema.shape has titleFormat. lookupFilters with operator:'='invalid_value@lookupFilters.0.operator; enum eq|ne|gt|lt|gte|lte|contains|in|notIn; with eq ACCEPTED. Both of the author's reported misses reproduce exactly. idField declarations: spec source has exactly one member declaration (packages/spec/src/data/field.zod.ts:836, inside InlineGridColumnSchema opened at :803; FieldSchema at :848); dist JS one definition per bundle; InlineGridColumnSchema.shape has idField.
  4. ListColumnSchema (/ui): {field:'name'} ACCEPTED; bogus key REJECTED; all 11 keys (display_field/displayField, id_field/idField, reference_to/reference, description_field/descriptionField, lookup_filters/lookupFilters, reference_field) REJECTED unrecognized_keys; shape is field,label,width,align,hidden,sortable,resizable,wrap,type,pinned,summary,prefix,link,action. ObjectSchema.fields as array → invalid_type@fields; as record ACCEPTED.
  5. Serve paths on main: packages/data-objectstack/src/index.ts .parse( 0, safeParse( 0, ObjectSchema/FieldSchema mentions 0 (controls DroppedFieldsEventSchema 4, isFilterAST 9); getObjectSchema at :3887metadataCache.getnormalizeSchemaReferenceKeys(schema) :3913applyFieldWidgetOverrides :3919 → return; four snake keys 0 in that package (control reference_to 8). MetadataProvider.tsx:648 getClient().meta.getItems(type) with objects: 'object' in the type map (:74); only parse is JSON.parse :515 (control expandViewContainer 5). @objectstack/client@17.2.0 dist: getItems is fetch(${baseUrl}/api/v1/meta/${type}) + unwrapResponse (:658-666, route :4366); safeParse on exactly 3 schemas (MetadataEventSchema, DataEventSchema, BulkDataEventSchema), 3 textual hits per build; control unwrapResponse 166.
  6. Camel reads off the bag on main (control = snake reads in the same run): ObjectChart fieldDef. 0/2; deriveMasterDetail d?. 0/2; ListView field. 0/2; UserFilters fieldDef. 0/2; resolveActionParams field. 0/4; ObjectFieldInspector def.lookupFilters 1 / .lookup_filters 1.
  7. Is any published contract on main still declaring the snake spellings? packages/types/src: 0 member declarations of the four (controls displayField?: 1, reference_to?: 4), 0 occurrences of the four at all. Repo-wide non-test member declarations: 5, all on non-published shapes — RuntimeField (resolveActionParams.ts:282, not exported; it is census site 6 itself) and FieldMeta (plugin-dashboard/src/recordFields.tsx:237, module-exported, not in the barrel; package.json exports "." only; barrel has no export *); control displayField?: 28.
  8. Inversion-bug card: semantic search for the designer-camel-first/runtime-snake-first disagreement returns 0 items; control query returns finding(data-objectstack): the object-schema SERVE path runs no parse, so FieldSchema strictness is not evidence that a refused key cannot reach a consumer #7650 first (19 total). No card exists.
  9. CI at head: 32 check runs, 29 success, 3 skipped (dependabot, coverage ×2), 0 failing; mergeable_state: clean, draft. Gates re-run by me on the head: check-changeset-no-major, check-changeset-presence ("EMPTY frontmatter … the explicit exemption"), check-changeset-fixed, check-changeset-overwrite, check-control-bytes (6247 files) — all exit 0. check:designer-field-key-parity NOT MEASURED locally (needs typescript, absent in the dep-less scratch tree); it runs in CI job Type Check (ci.yml:132, step :386), success at head.

① Derived judgments

Accept-set changes in the diff: 0. Published-surface changes in the diff: 0. Proof: fact 1 — every added line under packages/ is a // comment, nothing removed, and the transpile-emit and AST-token readings of all six files are identical to origin/main with the control lit on each file; the seventh file is a changeset with ---\n---\n frontmatter (verified cat -A); no .d.ts, barrel, package.json or packages/types/** is touched. The judgments the PR ships are the seven in-tree KEEP verdicts and the prose findings:

  1. ObjectChart.tsx KEEP — RIGHT. :659 ds.getObjectSchema(schema.objectName):660 resolveGroupByLabels:117 objectSchema?.fields?.[groupByField]; reads :197 id_field, :208 reference_field || display_field; camel legs 0 (fact 6); resolveGroupByLabels not in src/index.tsx (:16 exports ObjectChart, ObjectChartBlock), package exports "." only. Its idField sentence — RIGHT (fact 3, including "only on InlineGridColumnSchema").
  2. deriveMasterDetail.ts KEEP, both reads — RIGHT. deriveColumns public at plugin-form/src/index.tsx:68; in-repo producer MasterDetailForm.tsx:582 dataSource.getObjectSchema(d.childObject)deriveDetail :612; no d?.displayField leg; markers now at :249-254 and :309-310 covering reads :255 and :311.
  3. ListView.tsx columns branch KEEP, third-contract — RIGHT. f from schema.columns (:2698); ListColumnSchema refuses both castings of all keys (fact 4); population is finding(plugin-list): ListView's declared-columns branch reads reference_to and reference off a list COLUMN, and ListColumnSchema declares neither #7531's (its body is exactly that branch).
  4. ListView.tsx object-def branch KEEP — RIGHT. sole non-null producer :1416 setObjectDef(def):1414 dataSource.getObjectSchema(schema.objectName); camel legs 0.
  5. UserFilters.tsx KEEP — RIGHT. objectDef?: any (:90), exported at plugin-list/src/index.tsx:19-20; camel legs 0.
  6. resolveActionParams.ts KEEP — RIGHT, and its provenance sentence (ctx.objects = useMetadata().objects, the /api/v1/meta/object documents) — RIGHT (fact 5: route /api/v1/meta, objects:'object', AppContent.tsx:172 useMetadata()RecordDetailView:588 / useConsoleActionRuntime:223).
  7. ObjectFieldInspector.tsx KEEP — RIGHT, and the rewritten :1145-1152 comment ("reads lookupFilters ONLY since objectui#7641 (merged 2026-09-04)") — RIGHT on 6eebc54b6 (fact 2: LookupField.tsx:278); camel-first read :1153, writes camel back :1204-1208.

Prose findings, graded on the tree this PR lands in:

  • Changeset finding 1 (serve path runs no parse; resolveActionParams served via getItems, no parse; client's 3 safeParse all event schemas) — RIGHT (fact 5). getItems('object') is a fair paraphrase of getItems(type) with objects:'object'.
  • Finding 2 (five sites no camel leg; idField refused on FieldSchema like id_field; scoped to FieldSchema; LookupFieldMetadata.idField declared, field-types.ts:525, read by LookupField.tsx:262; id_field routed to finding(data-objectstack): the object-schema SERVE path runs no parse, so FieldSchema strictness is not evidence that a refused key cannot reach a consumer #7650 A) — RIGHT on every measured clause (facts 2, 3, 6).
  • Finding 3 (dated: at base snake-first / four snake members / lookup.mdx three; feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 merged 2026-09-04T15:01:32Z as 351eb318; on main camel-only with reference_field kept, LookupFieldMetadata camel-only, docs 0 with controls 4/3; the split now runs the other way; KEEP on 1-2 and the B-refused/A-prerequisite ruling) — RIGHT, every tense correct against 6eebc54b6 (fact 2), and the ruling quoted matches comment 5542090184 verbatim in substance.
  • Inversion paragraph ("deliberately NOT filed"; "RETIRED on main"; PM fallback moot) — RIGHT (facts 2, 8).
  • Per-site flip lines — RIGHT for six; one imprecision, non-blocking: ObjectChart "would have collapsed to the constants 'id' and 'name' for every host" — the display chain is reference_field || display_field || 'name' and reference_field is fenced, not retired, so a host emitting that (no-producer) key would not collapse to 'name'. A counterfactual about a retirement that did not happen; a tighter wording is "to 'id', and to reference_field || 'name'".
  • PR body: opening count (+145/−0) RIGHT; §1-§3, Measurements, "One live disagreement", both Remediation sections, Verification (gate lines re-run by me, all exit 0; token identity; round-2 hunk all-//; width 88) — RIGHT. main has moved 80 files since the base in Scope is now 96 (undated present tense; a count that drifts by construction) — the load-bearing halves, 0 overlap and a clean merge, hold (fact 1; mergeable_state: clean). Non-blocking; date it "as of a3eb5d07" if the body is touched again. The lookup_filters 3 count in §3 is content/docs-wide (lookup.mdx 2 + user.mdx 1); trivial.
  • Prior-verdict cross-check: both round-1 grounds (idField undeclared on FieldSchema; "filed" was false) re-confirmed by my own probe and search; the round-2 ground (feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase #7641 asserted open) re-confirmed as having been true then and fixed now. I reused none of their numbers.

② Semver grading

Declared: .changeset/lucky-donkeys-shave.md frontmatter ---\n---\nno bump. Correct level: none — zero executable tokens moved (fact 1), no export, no .d.ts, no runtime. AGENTS.md:235-241 (§版本号策略): major is refused mechanically (check-changeset-no-major.mjs, exit 0 here) and objectui-internal breaking changes ship as minor with the semantics in the body; neither applies. check-changeset-presence.mjs accepts the empty frontmatter as "the explicit exemption and a complete answer to this gate" (exit 0 on the head; Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report green). Declared = correct. The body — the platform's release-note input — is true on 6eebc54b6 in every present-tense sentence (①), with the one counterfactual imprecision noted above.

③ Boundary-flag disposition

Verdict

The prior three refusal grounds are fixed and independently confirmed; the diff is text-only by an instrument with a lit control on every file; all seven KEEPs are right; every present-tense claim in the changeset, the in-tree comments and the PR body is true on the current origin/main, the only tree this PR can land in; the declared no-bump is the correct level; and the contract-review trigger is measured dissolved on main. The residual findings (the ObjectChart counterfactual wording, the undated "80 files") are imprecisions that mislead no reader of the release notes and change no verdict.

VERDICT: PASS


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 5, 2026 06:57
@os-sam
os-sam added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 5b5d3ed Sep 5, 2026
34 checks passed
@os-sam
os-sam deleted the claude/issue-7642-object-schema-snake-lookup-census branch September 5, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants